﻿using Ganweisoft.IoTCenter.Module.MediaPlatform.Utilities;
using IoTCenter.Utilities;
using IoTCenter.Utilities.Extensions;
using IoTCenter.Utilities.Models;
using Microsoft.Extensions.Logging;
using Onvif.WebApi.Common;
using Onvif.WebApi.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace Onvif.WebApi.ServicesImpl
{
    public class OnvifDeviceRequestProxy : ApiHttpClient, IOnvifDeviceRequestProxy
    {
        private readonly ILogger<OnvifDeviceRequestProxy> _logger;

        public OnvifDeviceRequestProxy(
            IHttpClientFactory httpClientFactory,
            ILoggerFactory loggerFactory) : base(httpClientFactory, loggerFactory, nameof(OnvifDeviceRequestProxy))
        {
            if (loggerFactory == null)
            {
                throw new ArgumentNullException(nameof(loggerFactory));
            }

            _logger = loggerFactory.CreateLogger<OnvifDeviceRequestProxy>();
            Client.DefaultRequestHeaders.Accept.Clear();
        }

        public async Task<IDictionary<string, IEnumerable<NameValueObject>>> GetDeviceCapabilities(string ip, string userName, string password)
        {
            var dicResult = new Dictionary<string, IEnumerable<NameValueObject>>();

            var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");

            var nonce = GetNonce();
            if (nonce.IsEmpty())
            {
                return dicResult;
            }

            var passwordDigest = GetPasswordDigest(nonce, created, password);
            if (passwordDigest.IsEmpty())
            {
                return dicResult;
            }

            var rawBody = string.Format(Constant.CAPABILITIES_QUERY_MESSAGE, userName, passwordDigest, nonce, created);
            if (rawBody.IsEmpty())
            {
                return dicResult;
            }

            var content = new StringContent(rawBody, Encoding.UTF8, "application/soap+xml");

            string requestUri = $"http://{ip}/onvif/device_service";

            DateTime beginRequestTime = DateTime.Now;

            //var log = Client.ToFormatString();
            //_logger.LogWarning($"===============Client信息===============\r\n{log}\r\n{rawBody}");

            HttpResponseMessage response = null;

            try
            {
                response = await Client.PostAsync(requestUri, content);
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, $"GetProfiles服务不可用:{ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"GetProfiles服务不可用:{ex.Message}");
            }
            finally
            {
                // LogApiRequest(beginRequestTime, response);
                var responseLog = await response.ToFormatString();
                _logger.LogWarning($"===============Response信息===============\r\n{responseLog}");
            }

            if (response == null)
            {
                return dicResult;
            }

            if (!response.IsSuccessStatusCode)
            {
                // Console.WriteLine($"HttpStatus:{response.IsSuccessStatusCode}");
                return dicResult;
            }

            var responseContent = await response.Content.ReadAsStringAsync();
            if (responseContent.IsEmpty())
            {
                return dicResult;
            }

            responseContent = responseContent.ToReplace(@"(<|/)[a-z0-9\-]+:", "$1");
            if (responseContent.IsEmpty())
            {
                return dicResult;
            }

            var xmlElements = responseContent.ToXElement(Encoding.UTF8);
            if (xmlElements == null)
            {
                return dicResult;
            }

            var mediaNodes = xmlElements.Descendants("Media");
            if (mediaNodes.IsEmpty())
            {
                return dicResult;
            }
            var mediaNode = mediaNodes.First();
            if (mediaNode.Elements().HasValues())
            {
                var serviceUrl = mediaNode.Descendants("XAddr").FirstOrDefault()?.Value;
                if (serviceUrl.IsEmpty())
                {
                    return dicResult;
                }

                var results = new List<NameValueObject>();
                var versions = mediaNode.Descendants().Where(x => x.Name.LocalName.Contains("Profiles")).Select(x => x.Value);
                if (versions.HasValues())
                {
                    results.AddRange(versions.Select(x =>
                    {
                        var ver = x.ToInt();
                        if (ver != 10 || ver != 20)
                        {
                            if (ver <= 10)
                            {
                                x = "10";
                            }
                            else
                            {
                                x = "20";
                            }
                        }

                        return new NameValueObject() { Name = x, Value = serviceUrl };
                    }).Where(x => x.Name.HasValue()).ToList());
                }

                if (results.IsEmpty())
                {
                    results.Add(new NameValueObject() { Name = "20", Value = serviceUrl });
                }

                dicResult.Add("Media", results);
            }

            var ptzNode = xmlElements.Descendants("PTZ").FirstOrDefault();
            if (ptzNode != null)
            {
                var serviceUrl = ptzNode.Descendants("XAddr").FirstOrDefault()?.Value;
                if (serviceUrl.IsEmpty())
                {
                    return dicResult;
                }

                dicResult.Add("PTZ", new List<NameValueObject>()
                {
                    new NameValueObject()
                    {
                         Name ="PTZ",
                         Value = serviceUrl
                    }
                });
            }

            return dicResult;
        }

        public async Task<IEnumerable<NameValueObject>> GetDeviceProfiles(string ip, string userName, string password, string ver, string url)
        {
            var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");

            var nonce = GetNonce();
            if (nonce.IsEmpty())
            {
                return Enumerable.Empty<NameValueObject>();
            }

            var passwordDigest = GetPasswordDigest(nonce, created, password);
            if (passwordDigest.IsEmpty())
            {
                return Enumerable.Empty<NameValueObject>();
            }

            var rawBody = string.Format(Constant.PROFILES_QUERY_MESSAGE, userName, passwordDigest, nonce, created, ver);
            if (rawBody.IsEmpty())
            {
                return Enumerable.Empty<NameValueObject>();
            }

            var content = new StringContent(rawBody, Encoding.UTF8, "application/soap+xml");

            DateTime beginRequestTime = DateTime.Now;

            //var log = Client.ToFormatString();
            //_logger.LogWarning($"===============Client信息===============\r\n{log}\r\n{rawBody}");

            HttpResponseMessage response = null;

            try
            {
                response = await Client.PostAsync(url, content);
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, $"GetProfiles服务不可用:{ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"GetProfiles服务不可用:{ex.Message}");
            }
            finally
            {
                // LogApiRequest(beginRequestTime, response);
                var responseLog = await response.ToFormatString();
                _logger.LogWarning($"===============Response信息===============\r\n{responseLog}");
            }

            if (response == null)
            {
                return Enumerable.Empty<NameValueObject>();
            }

            if (!response.IsSuccessStatusCode)
            {
                // Console.WriteLine($"HttpStatus:{response.IsSuccessStatusCode}");
                return Enumerable.Empty<NameValueObject>();
            }

            var responseContent = await response.Content.ReadAsStringAsync();
            if (responseContent.IsEmpty())
            {
                return Enumerable.Empty<NameValueObject>();
            }

            responseContent = responseContent.ToReplace(@"(<|/)[a-z0-9\-]+:", "$1");
            if (responseContent.IsEmpty())
            {
                return Enumerable.Empty<NameValueObject>();
            }

            var xmlElements = responseContent.ToXElement(Encoding.UTF8);
            if (xmlElements == null)
            {
                return Enumerable.Empty<NameValueObject>();
            }

            var profiles = xmlElements.Descendants("Profiles");
            if (profiles.IsEmpty())
            {
                return Enumerable.Empty<NameValueObject>();
            }

            var results = new List<NameValueObject>();
            foreach (var profile in profiles)
            {
                var name = profile.Descendants("Name").Select(x => x.Value).FirstOrDefault();
                var token = profile.Attributes("token").Select(x => x.Value).FirstOrDefault();
                if (token.IsEmpty())
                {
                    continue;
                }
                if (name.IsEmpty())
                {
                    name = token;
                }

                results.Add(new NameValueObject() { Name = name, Value = token });
            }
            return results;
        }

        public async Task<string> GetDeviceStreamUri(string ip, string userName, string password, string token, string ver, string url)
        {
            var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");

            var nonce = GetNonce();
            if (nonce.IsEmpty())
            {
                return string.Empty;
            }

            var passwordDigest = GetPasswordDigest(nonce, created, password);
            if (passwordDigest.IsEmpty())
            {
                return string.Empty;
            }

            var rawBody = string.Format(Constant.STREAM_URI_QUERY_MESSAGE, userName, passwordDigest, nonce, created, token, ver);
            if (rawBody.IsEmpty())
            {
                return string.Empty;
            }

            var content = new StringContent(rawBody, Encoding.UTF8, "application/soap+xml");

            DateTime beginRequestTime = DateTime.Now;

            HttpResponseMessage response = null;

            try
            {
                response = await Client.PostAsync(url, content);
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, $"GetStreamUri服务不可用:{ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"GetStreamUri服务不可用:{ex.Message}");
            }
            finally
            {
                // LogApiRequest(beginRequestTime, response);
                var responseLog = await response.ToFormatString();
                _logger.LogWarning($"===============Response信息===============\r\n{responseLog}");
            }

            if (response == null)
            {
                return string.Empty;
            }

            if (!response.IsSuccessStatusCode)
            {
                return string.Empty;
            }

            var responseContent = await response.Content.ReadAsStringAsync();
            if (responseContent.IsEmpty())
            {
                return string.Empty;
            }

            responseContent = responseContent.ToReplace(@"(<|/)[a-z0-9\-]+:", "$1");
            if (responseContent.IsEmpty())
            {
                return string.Empty;
            }

            // todo:
            var xmlElements = responseContent.ToXElement(Encoding.UTF8);
            if (xmlElements == null)
            {
                return string.Empty;
            }

            var streamUri = xmlElements.Descendants("Uri").Select(x => x.Value).FirstOrDefault();
            if (streamUri.IsEmpty())
            {
                return string.Empty;
            }

            var uri = new Uri(streamUri);
            return $"{uri.Scheme}://{userName}:{password}@{uri.Host}:{uri.Port}{uri.AbsolutePath}";
        }

        public async Task<string> PTZContinuousMove(string ip, string userName, string password, string token, string ver, string url, double pan, double tilt, double zoom)
        {
            var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");

            var nonce = GetNonce();
            if (nonce.IsEmpty())
            {
                return string.Empty;
            }

            var passwordDigest = GetPasswordDigest(nonce, created, password);
            if (passwordDigest.IsEmpty())
            {
                return string.Empty;
            }

            var rawBody = string.Format(Constant.CONTINUOUS_MOVE_PTZ_MESSAGE, userName, passwordDigest, nonce, created, token, ver, pan, tilt, zoom);
            if (rawBody.IsEmpty())
            {
                return string.Empty;
            }

            var content = new StringContent(rawBody, Encoding.UTF8, "application/soap+xml");

            DateTime beginRequestTime = DateTime.Now;

            HttpResponseMessage response = null;

            try
            {
                response = await Client.PostAsync(url, content);
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            finally
            {
                // LogApiRequest(beginRequestTime, response);
                var responseLog = await response.ToFormatString();
                _logger.LogWarning($"===============Response信息===============\r\n{responseLog}");
            }

            if (response == null)
            {
                return string.Empty;
            }

            if (!response.IsSuccessStatusCode)
            {
                return string.Empty;
            }

            var responseContent = await response.Content.ReadAsStringAsync();
            if (responseContent.IsEmpty())
            {
                return string.Empty;
            }

            return string.Empty;
        }

        public async Task<IEnumerable<PresetItemResponseModel>> GetPresets(string ip, string userName, string password, string token, string ver, string url)
        {
            var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");

            var nonce = GetNonce();
            if (nonce.IsEmpty())
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            var passwordDigest = GetPasswordDigest(nonce, created, password);
            if (passwordDigest.IsEmpty())
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            var rawBody = string.Format(Constant.PTZ_PRESET_QUERY_MESSAGE, userName, passwordDigest, nonce, created, token, ver);
            if (rawBody.IsEmpty())
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            var content = new StringContent(rawBody, Encoding.UTF8, "application/soap+xml");

            DateTime beginRequestTime = DateTime.Now;

            HttpResponseMessage response = null;

            try
            {
                response = await Client.PostAsync(url, content);
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            finally
            {
                // LogApiRequest(beginRequestTime, response);
                var responseLog = await response.ToFormatString();
                _logger.LogWarning($"===============Response信息===============\r\n{responseLog}");
            }

            if (response == null)
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            if (!response.IsSuccessStatusCode)
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            var responseContent = await response.Content.ReadAsStringAsync();
            if (responseContent.IsEmpty())
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            responseContent = responseContent.ToReplace(@"(<|/)[a-z0-9\-]+:", "$1");
            if (responseContent.IsEmpty())
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            var xmlElements = responseContent.ToXElement(Encoding.UTF8);
            if (xmlElements == null)
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            var presets = xmlElements.Descendants("Preset");
            if (presets.IsEmpty())
            {
                return Enumerable.Empty<PresetItemResponseModel>();
            }

            var results = new List<PresetItemResponseModel>();

            var index = 0;
            foreach (var preset in presets)
            {
                index++;

                var id = 0;
                var tokenValue = preset.Attributes("token").Select(x => x.Value).FirstOrDefault();
                if (tokenValue.HasValue())
                {
                    id = tokenValue.ToInt();
                    if (id == 0)
                    {
                        id = index;
                    }
                }
                var name = preset.Descendants("Name").Select(x => x.Value).FirstOrDefault();

                // position
                var position = new PresetPositionItemModel();
                var panTilt = preset.Descendants("PanTilt").FirstOrDefault();
                if (panTilt != null)
                {
                    var pan = panTilt.Attributes("x").Select(x => x.Value).FirstOrDefault();
                    if (pan.HasValue())
                    {
                        position.Pan = pan.ToDouble(6);
                    }
                    var tilt = panTilt.Attributes("y").Select(x => x.Value).FirstOrDefault();
                    if (tilt.HasValue())
                    {
                        position.Tilt = tilt.ToDouble(6);
                    }
                }

                var zoom = preset.Descendants("Zoom").FirstOrDefault();
                if (zoom != null)
                {
                    var z = panTilt.Attributes("x").Select(x => x.Value).FirstOrDefault();
                    if (z.HasValue())
                    {
                        position.Zoom = z.ToDouble(6);
                    }
                }

                results.Add(new PresetItemResponseModel()
                {
                    PresetId = id,
                    PresetName = name,
                    Position = position
                });
            }

            return results;
        }

        public async Task<OperateResult> GotoPresets(string ip, string userName, string password, string token, string ver, string url, string presetToken)
        {
            var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");

            var nonce = GetNonce();
            if (nonce.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var passwordDigest = GetPasswordDigest(nonce, created, password);
            if (passwordDigest.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var rawBody = string.Format(Constant.PTZ_PRESET_GOTO_MESSAGE, userName, passwordDigest, nonce, created, token, ver, presetToken);
            if (rawBody.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var content = new StringContent(rawBody, Encoding.UTF8, "application/soap+xml");

            DateTime beginRequestTime = DateTime.Now;

            HttpResponseMessage response = null;

            try
            {
                response = await Client.PostAsync(url, content);
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            finally
            {
                // LogApiRequest(beginRequestTime, response);
                var responseLog = await response.ToFormatString();
                _logger.LogWarning($"===============Response信息===============\r\n{responseLog}");
            }

            if (response == null)
            {
                return OperateResult.Failed();
            }

            var responseContent = await response.Content.ReadAsStringAsync();
            if (responseContent.IsEmpty())
            {
                return OperateResult.Failed();
            }

            if (!response.IsSuccessStatusCode)
            {
                return OperateResult.Failed(responseContent);
            }

            return OperateResult.Success;
        }


        public async Task<OperateResult> SetPresets(string ip, string userName, string password, string token, string ver, string url, string presetName, string presetToken)
        {
            var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");

            var nonce = GetNonce();
            if (nonce.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var passwordDigest = GetPasswordDigest(nonce, created, password);
            if (passwordDigest.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var rawBody = string.Format(Constant.PTZ_PRESET_SET_MESSAGE, userName, passwordDigest, nonce, created, token, ver, presetName, presetToken);
            if (rawBody.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var content = new StringContent(rawBody, Encoding.UTF8, "application/soap+xml");

            DateTime beginRequestTime = DateTime.Now;

            HttpResponseMessage response = null;

            try
            {
                response = await Client.PostAsync(url, content);
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            finally
            {
                // LogApiRequest(beginRequestTime, response);
                var responseLog = await response.ToFormatString();
                _logger.LogWarning($"===============Response信息===============\r\n{responseLog}");
            }

            if (response == null)
            {
                return OperateResult.Failed();
            }

            var responseContent = await response.Content.ReadAsStringAsync();
            if (responseContent.IsEmpty())
            {
                return OperateResult.Failed();
            }

            if (!response.IsSuccessStatusCode)
            {
                return OperateResult.Failed(responseContent);
            }

            return OperateResult.Success;
        }

        public async Task<OperateResult> RemovePresets(string ip, string userName, string password, string token, string ver, string url, string presetToken)
        {
            var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");

            var nonce = GetNonce();
            if (nonce.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var passwordDigest = GetPasswordDigest(nonce, created, password);
            if (passwordDigest.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var rawBody = string.Format(Constant.PTZ_PRESET_REMOVE_MESSAGE, userName, passwordDigest, nonce, created, token, ver, presetToken);
            if (rawBody.IsEmpty())
            {
                return OperateResult.Failed();
            }

            var content = new StringContent(rawBody, Encoding.UTF8, "application/soap+xml");

            DateTime beginRequestTime = DateTime.Now;

            HttpResponseMessage response = null;

            try
            {
                response = await Client.PostAsync(url, content);
            }
            catch (ApiException ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"PTZ服务不可用:{ex.Message}");
            }
            finally
            {
                // LogApiRequest(beginRequestTime, response);
                var responseLog = await response.ToFormatString();
                _logger.LogWarning($"===============Response信息===============\r\n{responseLog}");
            }

            if (response == null)
            {
                return OperateResult.Failed();
            }

            if (!response.IsSuccessStatusCode)
            {
                return OperateResult.Failed();
            }

            var responseContent = await response.Content.ReadAsStringAsync();
            if (responseContent.IsEmpty())
            {
                return OperateResult.Failed();
            }

            return OperateResult.Success;
        }

        private string GetNonce()
        {
            byte[] nonce = new byte[16];
            new Random().NextBytes(nonce);

            try
            {
                return Convert.ToBase64String(nonce);
            }
            catch
            {

            }

            return string.Empty;
        }

        private string GetPasswordDigest(string nonce, string created, string password)
        {
            try
            {
                var _nonce = Convert.FromBase64String(nonce);
                if (_nonce.IsEmpty())
                {
                    return string.Empty;
                }

                var _created = Encoding.UTF8.GetBytes(created);
                if (_created.IsEmpty())
                {
                    return string.Empty;
                }

                var data = _nonce.Concat(_created);
                if (data.IsEmpty())
                {
                    return string.Empty;
                }

                var _password = Encoding.UTF8.GetBytes(password);
                if (_password.IsEmpty())
                {
                    return string.Empty;
                }

                data = data.Concat(_password);

                var encryptText = HashAlgorithm.Create("SHA1").ComputeHash(data.ToArray());
                if (encryptText.IsEmpty())
                {
                    return string.Empty;
                }

                return Convert.ToBase64String(encryptText);
            }
            catch
            {

            }

            return string.Empty;
        }
    }


    /// <summary>
    /// 预置位信息
    /// </summary>
    public class PresetItemResponseModel
    {
        /// <summary>
        /// 预置位编码
        /// </summary>
        [XmlElement("PresetID")]
        public int PresetId { get; set; }

        /// <summary>
        /// 预置位名称
        /// </summary>
        [XmlElement("PresetName")]
        public string PresetName { get; set; }
        public PresetPositionItemModel Position { get; set; }
    }

    public class PresetPositionItemModel
    {
        public double? Pan { get; set; }
        public double? Tilt { get; set; }
        public double? Zoom { get; set; }
    }
}